/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Globalization;
using DaveSexton.DocProject.Resources;

namespace DaveSexton.DocProject
{
	[Serializable]
	public class SettingsDictionary<TKey, TValue> : IDictionary<TKey, TValue>, ISerializable
	{
		#region Public Properties
		public CultureInfo Culture
		{
			get
			{
				return culture;
			}
		}
		#endregion

		#region Private / Protected
		private readonly Dictionary<TKey, TValue> data;
		private readonly CultureInfo culture;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary()
		{
			culture = CultureInfo.InvariantCulture;
			data = new Dictionary<TKey, TValue>();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(int capacity)
		{
			culture = CultureInfo.InvariantCulture;
			data = new Dictionary<TKey, TValue>(capacity);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(IEqualityComparer<TKey> comparer)
		{
			culture = CultureInfo.InvariantCulture;
			data = new Dictionary<TKey, TValue>(comparer);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(int capacity, IEqualityComparer<TKey> comparer)
		{
			culture = CultureInfo.InvariantCulture;
			data = new Dictionary<TKey, TValue>(capacity, comparer);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(CultureInfo culture)
		{
			if (culture == null)
				throw new ArgumentNullException("culture");

			this.culture = culture;
			data = new Dictionary<TKey, TValue>();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(CultureInfo culture, int capacity)
		{
			if (culture == null)
				throw new ArgumentNullException("culture");

			this.culture = culture;
			data = new Dictionary<TKey, TValue>(capacity);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(CultureInfo culture, IEqualityComparer<TKey> comparer)
		{
			if (culture == null)
				throw new ArgumentNullException("culture");

			this.culture = culture;
			data = new Dictionary<TKey, TValue>(comparer);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class.
		/// </summary>
		public SettingsDictionary(CultureInfo culture, int capacity, IEqualityComparer<TKey> comparer)
		{
			if (culture == null)
				throw new ArgumentNullException("culture");

			this.culture = culture;
			data = new Dictionary<TKey, TValue>(capacity, comparer);
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="SettingsDictionary" /> class for deserialization.
		/// </summary>
		protected SettingsDictionary(SerializationInfo info, StreamingContext context)
		{
			culture = (CultureInfo) info.GetValue("culture", typeof(CultureInfo));
			data = (Dictionary<TKey, TValue>) info.GetValue("data", typeof(Dictionary<TKey, TValue>));
		}
		#endregion

		#region Methods
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public TTo ConvertType<TFrom, TTo>(TFrom value)
		{
			return ConvertType<TFrom, TTo>(value, culture);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes")]
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public static TTo ConvertType<TFrom, TTo>(TFrom value, CultureInfo culture)
		{
			Type from = typeof(TFrom);
			Type to = typeof(TTo);

			if (to.IsAssignableFrom(from))
				return (TTo) (object) value;
			else if (value == null)
				return default(TTo);
			else if (to == typeof(string) && (from == typeof(IEnumerable<string>) || from == typeof(ICollection<string>)))
				return (TTo) (object) StringCollectionConverter.Default.ConvertToString(null, culture, value);
			else if (from == typeof(string) && (to == typeof(IEnumerable<string>) || to == typeof(ICollection<string>)))
				return (TTo) (object) StringCollectionConverter.Default.ConvertFromString(null, culture, (string) (object) value);

			TypeConverter converter = TypeDescriptor.GetConverter(to);

			if (converter != null && converter.CanConvertFrom(from))
				return (TTo) converter.ConvertFrom(null, culture, value);

			converter = TypeDescriptor.GetConverter(from);

			if (converter != null && converter.CanConvertTo(to))
				return (TTo) converter.ConvertTo(null, culture, value, to);

			if (value is IConvertible)
				return (TTo) Convert.ChangeType(value, to, culture);

			throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture, 
				Errors.SettingsDictionaryCannotConvertTo, from, to));
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
		public T Read<T>(TKey name)
		{
			return Read<T>(name, culture, default(T));
		}

		public T Read<T>(TKey name, T defaultValue)
		{
			return Read<T>(name, culture, defaultValue);
		}

		public virtual T Read<T>(TKey name, CultureInfo cultureInfo, T defaultValue)
		{
			if (!this.ContainsKey(name))
				return defaultValue;

			return ConvertType<TValue, T>(this[name], cultureInfo);
		}

		public void Write<T>(TKey name, T value)
		{
			Write(name, value, culture);
		}

		public virtual void Write<T>(TKey name, T value, CultureInfo cultureInfo)
		{
			this[name] = ConvertType<T, TValue>(value, cultureInfo);
		}
		#endregion

		#region IDictionary<TKey,TValue> Members
		public virtual void Add(TKey key, TValue value)
		{
			data.Add(key, value);
		}

		public virtual bool ContainsKey(TKey key)
		{
			return data.ContainsKey(key);
		}

		public virtual ICollection<TKey> Keys
		{
			get
			{
				return data.Keys;
			}
		}

		public virtual bool Remove(TKey key)
		{
			return data.Remove(key);
		}

		public bool TryGetValue(TKey key, out TValue value)
		{
			try
			{
				value = this[key];
				return true;
			}
			catch (KeyNotFoundException) { }

			value = default(TValue);
			return false;
		}

		public virtual ICollection<TValue> Values
		{
			get
			{
				return data.Values;
			}
		}

		public virtual TValue this[TKey key]
		{
			get
			{
				return data[key];
			}
			set
			{
				data[key] = value;
			}
		}
		#endregion

		#region ICollection<KeyValuePair<TKey,TValue>> Members
		void ICollection<KeyValuePair<TKey,TValue>>.Add(KeyValuePair<TKey, TValue> item)
		{
			Add(item.Key, item.Value);
		}

		public virtual void Clear()
		{
			data.Clear();
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
		bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
		{
			return ContainsKey(item.Key) && EqualityComparer<TValue>.Default.Equals(this[item.Key], item.Value);
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
		void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
		{
			((ICollection<KeyValuePair<TKey, TValue>>) data).CopyTo(array, arrayIndex);
		}

		public virtual int Count
		{
			get
			{
				return data.Count;
			}
		}

		public virtual bool IsReadOnly
		{
			get
			{
				return false;
			}
		}

		bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
		{
			if (ContainsKey(item.Key) && EqualityComparer<TValue>.Default.Equals(this[item.Key], item.Value))
			{
				Remove(item.Key);
				return true;
			}

			return false;
		}
		#endregion

		#region IEnumerable<KeyValuePair<TKey,TValue>> Members
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
		{
			return data.GetEnumerator();
		}
		#endregion

		#region IEnumerable Members
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}
		#endregion

		#region ISerializable Members
		[System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, SerializationFormatter = true)]
		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("culture", culture);
			info.AddValue("data", data);
		}
		#endregion
	}
}
